package server;

import java.io.File;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.DatagramChannel;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;

public class Server {

    public static void log(String tag, String msg) {
         System.out.println(String.format("[%s] %s", tag, msg));
    }

    public static int S = 5;
    private int USER_PORT = 15000;
    private int SERVER_PORT = 14000;
    private int LIVE_PORT = 13000;
    public static int LOAD = 50;
    private String DATA_FILE;
    public static final String ENCODING = "UTF-8";
    public static String EMPTY_ID = Crypto.hash("");


    public static void main(String[] args) {
        Server serv = new Server();
        switch (args.length) {
        case 2:
            serv.USER_PORT += Integer.parseInt(args[0]);
            serv.SERVER_PORT = serv.USER_PORT - 1000;
            serv.LIVE_PORT = serv.SERVER_PORT - 1000;
            serv.DATA_FILE = args[1];
            break;
        case 1:
            serv.DATA_FILE = args[0];
            break;
        default:
            File f = new File("config.txt");
            if (f.exists()) {
                serv.DATA_FILE = "config.txt";
            } else {
                System.out
                        .println("Usage: java Server config_file [default config.txt]");
                System.exit(-1);
            }
        }
        
        Map<String, Map<SocketAddress, Integer>> addrSeen = new ConcurrentHashMap<String, Map<SocketAddress, Integer>>();
		addrSeen.put(Server.EMPTY_ID, new ConcurrentHashMap<SocketAddress, Integer>());

        /* Outgoing queue to connected users/clients */
        BlockingQueue<Message> uOutgoing = new LinkedBlockingQueue<Message>();
        /* Outgoing queue to connected servers */
        BlockingQueue<Message> sOutgoing = new LinkedBlockingQueue<Message>();
        /* Queue for connection requests */
        BlockingQueue<Message> connections = new LinkedBlockingQueue<Message>();

        /* Maps publisher ids to their socket address */
        ConcurrentHashMap<String, SocketAddress> uAddrs = new ConcurrentHashMap<String, SocketAddress>();
        
        ConcurrentHashMap<String, Long> lastSeq = new ConcurrentHashMap<String, Long>();
        
        /* List of peer servers */
        Map<String, CopyOnWriteArrayList<SocketAddress>> sAddrs = new HashMap<String, CopyOnWriteArrayList<SocketAddress>>();
        sAddrs.put(EMPTY_ID, initMesh());
        
        new LivenessThread(serv.DATA_FILE, serv.LIVE_PORT, sAddrs, uAddrs, addrSeen, connections).start();
        
        /* Try to set up a socket to listen on for clients */
        DatagramChannel uSock;
        try {
            uSock = DatagramChannel.open();
            uSock.socket().bind(new InetSocketAddress(serv.USER_PORT));
            uSock.socket().setSendBufferSize(524288);
            uSock.configureBlocking(false);

            new UserRecvThread(uOutgoing, uAddrs, uSock, sOutgoing, sAddrs, serv.LIVE_PORT, connections, lastSeq).start();
            new UserSendThread(uOutgoing, uAddrs, uSock, sAddrs, serv.LIVE_PORT).start();
        } catch (IOException e) {
            System.exit(-1);
        }
                
        new ConnectorThread(connections, uOutgoing, uAddrs, addrSeen).start();


        DatagramSocket sSock;
        try {
            sSock = new DatagramSocket(serv.SERVER_PORT);

            new ServerRecvThread(uOutgoing, sOutgoing, sSock, lastSeq).start();
            new ServerSendThread(sAddrs, sOutgoing, sSock).start();
        } catch (IOException e) {
            System.exit(-1);
        }
    }

    public static int getEmptyIndex(List<SocketAddress> sAddrs) {
        for (int i = 0; i < 5; i++) {
            if (sAddrs.get(i) == null)
                return i;
        }
        return -1;
    }
    
    public static CopyOnWriteArrayList<SocketAddress> initMesh() {
        CopyOnWriteArrayList<SocketAddress> list = new CopyOnWriteArrayList<SocketAddress>();
        for (int i = 0; i < S; i++)
            list.add(null);
        
        return list;
    }
    
    public static int calculateLoad(int connected) {
    	return LOAD - connected;
    }
}